旧游无处不堪寻
无寻处,惟有少年心
HTTP 缓存

计算机科学只有两大难题: 命名和缓存失效。

Cache 的设计是个基础计算机理论,也是程序员的重要基本功之一。Cache 几乎无处不在,CPU 的 L1 L2 L3 Cache、iOS 系统的 clean page 和 dirty page 机制、HTTP 的 tag 机制等等,这些背后都是 Cache 设计思想的应用。
上一篇介绍了关于 HTTP 的基础知识,本篇对 HTTP Cache 做一个完整的介绍。

HTTP 头信息控制缓存


分为两种

  1. 强制缓存
  2. 协商缓存

强制缓存如果命中缓存则不需要和服务器端发生交互,而协商缓存不管是否命中都要和服务器端发生交互,强制缓存的优先级高于协商缓存。

匹配流程如下:
客户端发起请求,根据 Expires/Cache-Control 判断是否命中强制缓存

  • 若命中,则从缓存获取资源
  • 未命中,则发送请求给服务器
    • 根据响应的 Last-Modified/ETag 判断是否命中协商缓存
      • 若命中,则从缓存获取资源
      • 未命中,则使用服务器端返回的资源

强制缓存


可以理解为无须验证的缓存策略。对强制缓存来说,响应头中有两个字段 Expires/Cache-Control 来表明规则。

Expires

Expires 指缓存过期的时间,超过了这个时间点就代表资源过期。有一个问题是由于使用具体时间,如果时间表示出错或者没有转换到正确的时区都可能造成缓存生命周期出错。并且 Expires 是 HTTP/1.0 的标准,现在更倾向于用 HTTP/1.1 中定义的 Cache-Control。两个字段同时存在时也是 Cache-Control 的优先级更高。

Cache-Control

Cache-Control 可以由多个字段组合而成,主要有以下几个取值:

  • max-age: 指定一个时间长度,在这个时间段内缓存是有效的,单位是 s。例如设置 Cache-Control:max-age=31536000,也就是说缓存有效期为 31536000/24/(60 * 60) 天。在没有禁用缓存并且没有超过有效时间的情况下,再次访问这个资源就命中了缓存,不会向服务器请求资源而是直接从浏览器缓存中取
  • public: 表明响应可以被任何对象(发送请求的客户端、代理服务器等等)缓存
  • private: 表明响应只能被单个用户(可能是操作系统用户、浏览器用户)缓存,是非共享的,不能被代理服务器缓存
  • no-cache: 强制所有缓存了该响应的用户,在使用已缓存的数据前,发送带验证器的请求到服务器。不是字面意思上的不缓存(也就是说使用 no-cache 必须经过协商缓存)
  • no-store: 禁止缓存,每次请求都要向服务器重新获取数据

协商缓存


缓存的资源到期了,并不意味着资源内容发生了改变,如果和服务器上的资源没有差异,实际上没有必要再次请求。客户端和服务器端通过某种验证机制验证当前请求资源是否可以使用缓存。

浏览器第一次请求数据之后会将数据和响应头部的缓存标识存储起来。再次请求时会带上存储的头部字段,服务器端验证是否可用。如果返回 304 Not Modified,代表资源没有发生改变可以使用缓存的数据,获取新的过期时间。反之返回 200 就相当于重新请求了一遍资源并替换旧资源。

Last-modified/If-Modified-Since

Last-modified: 服务器端资源的最后修改时间,响应头部会带上这个标识。第一次请求之后,浏览器记录这个时间,再次请求时,请求头部带上 If-Modified-Since 即为之前记录下的时间。服务器端收到带 If-Modified-Since 的请求后会去和资源的最后修改时间对比。若修改过就返回最新资源,状态码 200,若没有修改过则返回 304。

Etag/If-None-Match

由服务器端上生成的一段 hash 字符串,第一次请求时响应头带上 ETag:abcd,之后的请求中带上 If-None-Match:abcd,服务器检查 ETag,返回 304 或 200。
流程可以参考 Google 网站上的下图:

Last-Modified 和 ETag 作为标识的不同

  1. 一些资源的最后修改时间改变了,但是内容没改变,使用 Last-modified 看不出内容没有改变
  2. Etag 的精度比 Last-modified 高,属于强验证,要求资源字节级别的一致,优先级高。如果服务器端有提供 ETag 的话,必须先对 ETag 进行 Conditional Request
  3. ETag 在分布式系统中生成的值可能不一样,会导致缓存失效

实际应用


考虑缓存的内容

  • css 文件
  • js 文件
  • logo、图标
  • html 文件

不考虑缓存的内容

  • 业务敏感的 GET 请求

可缓存的内容又分为几种不同的情况

不经常改变的文件

max-age=31536000
比如引入的一些第三方文件、打包出来的带有 hash 后缀 css、js 文件。一般来说文件内容改变了,会更新版本号、hash 值,相当于请求另一个文件。

标准中规定 max-age 的值最大不超过一年,所以设成 max-age=31536000。至于过期内容,缓存区会将一段时间没有使用的文件删除掉。

可能经常需要变动的文件

Cache-Control: no-cache / max-age=0
比如入口 index.html 文件、文件内容改变但名称不变的资源。选择 ETag 或 Last-Modified 来做验证,在使用缓存资源之前一定会去服务器端做验证,命中缓存时会比第一种情况慢一点点,毕竟还要发请求进行通信。